package com.luo.chengrui.framework.sadatapermission.core.rule.dept;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import com.luo.chengrui.module.sa.api.permission.SaPermissionApi;
import com.luo.chengrui.module.sa.api.permission.dto.UserDataPermissionRespDTO;
import com.luo.chengrui.module.sa.enums.LikeOrInEnum;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
import com.luo.chengrui.framework.sadatapermission.core.annotation.SaDataPermission;
import com.luo.chengrui.framework.sadatapermission.core.aop.DataPermissionContextHolder;
import com.luo.chengrui.framework.sadatapermission.core.rule.DataPermissionRule;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.NullValue;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
import net.sf.jsqlparser.util.cnfexpression.MultiOrExpression;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 参考 {@link cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule} 数据权限实现。
 * 扩展为可基于数据权限定义，可将任意表作为权限控制表。甚至任意数据集合作为权限控制依据。可满足任意业务场景下数据权限的控制。
 * <p>
 * 一、数据权限定义，支持两种类型：
 * 1、严格按表名控制（目标语句中包含表名时，对该表进行权限控制。）；
 * 2、数据集合，按外键设置控制（如果目标表中包含外键字段，则对该表进行权限控制）
 * 二、生成控制表达
 * 1、按使用 字段 in(xx，xx,...)的格式
 * 2、使用权限明细表，进行关联查询（这需要在配置权限时，生成每个人的明细数据，数据量较大，因此也是无法做权限缓存）
 *
 * @author luodz
 */
@AllArgsConstructor
@Slf4j
public class SaDataPermissionRuleImpl implements DataPermissionRule {

    /**
     * LoginUser 的 Context 缓存 Key
     */
    protected static final String CONTEXT_KEY = DataPermissionRule.class.getSimpleName();


    static final Expression EXPRESSION_NULL = new NullValue();

    private final SaPermissionApi saPermissionApi;
    private final PermissionApi permissionApi;

    @Override
    public Expression getExpression(String tableName, Alias tableAlias) {

        SaDataPermission saDataPermission = DataPermissionContextHolder.get();
        if (saDataPermission == null || !saDataPermission.enable()) {
            return null;
        }
        if (StrUtil.isEmpty(saDataPermission.permissinCode())) {
            // 如果权限项编号为空，无权限。
            throw new RuntimeException("注解SaDataPermission未设置permissionSubCode");
        }

        // 只有有登陆用户的情况下，才进行数据权限的处理
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        if (loginUser == null) {
            return null;
        }
        // 只有管理员类型的用户，才进行数据权限的处理
        if (ObjectUtil.notEqual(loginUser.getUserType(), UserTypeEnum.ADMIN.getValue())) {
            return null;
        }

        // 超管和租户管理员不控制权限。
        if (permissionApi.hasAnyRoles(loginUser.getId(), RoleCodeEnum.SUPER_ADMIN.getCode(), RoleCodeEnum.TENANT_ADMIN.getCode()).getCheckedData()) {
            return null;
        }

        // 获得数据权限
        UserDataPermissionRespDTO dataPermission = loginUser.getContext(CONTEXT_KEY, UserDataPermissionRespDTO.class);
        // 从上下文中拿不到，则调用逻辑进行获取
        if (dataPermission == null) {
            dataPermission = saPermissionApi.getUserDataPermissionByCode(loginUser.getId(), saDataPermission.permissinCode()).getCheckedData();

            if (dataPermission == null) {
                log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser));
                return new EqualsTo(null, null);
            }
            // 添加到上下文中，避免重复计算
            loginUser.setContext(CONTEXT_KEY, dataPermission);
        }

        // 有所有权限
        if (dataPermission.isAll()) {
            return null;
        }

        String permissionColumn = "";
        String likeColumn = "";
        //权限定义的表名，非物理表，则表名为空
        String configTableName = dataPermission.getIsDbTable() == 0 ? "" : dataPermission.getTableName();
        //注解自定义的表名，未配置默认为定义的表名
        String targetTableName = StrUtil.isEmpty(saDataPermission.targetTable()) ? configTableName : saDataPermission.targetTable();

        if (StrUtil.isEmpty(targetTableName)) {
            throw new RuntimeException("请指定需要控制权限的目标表名，通过SaDataPermission的targetTable的属性指定");
        }
        if (!Objects.equals(tableName, targetTableName)) {
            return null;
        }
        if (Objects.equals(configTableName, tableName)) {
            // 配置表和目标表相同，则使用主键控制权限；
            permissionColumn = StrUtil.isEmpty(saDataPermission.targetColumn()) ? dataPermission.getPrimaryKeyName() : saDataPermission.targetColumn();
        } else {
            permissionColumn = StrUtil.isEmpty(saDataPermission.targetColumn()) ? dataPermission.getForeignKeyName() : saDataPermission.targetColumn();
        }
        likeColumn = Objects.isNull(saDataPermission.likeTargetColumn()) ? permissionColumn : saDataPermission.likeTargetColumn();

        List<Expression> multeOrExpression = new ArrayList<>();
        for (UserDataPermissionRespDTO.ConfigData configData : dataPermission.getConfigList()) {
            if (CollUtil.isEmpty(configData.getDataIds())) {
                continue;
            }
            if (Objects.equals(LikeOrInEnum.IN.getId(), configData.getLikeOrIn())) {
                Expression subExpression = new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, permissionColumn),
                        new ExpressionList(CollectionUtils.convertList(configData.getDataIds(), StringValue::new)));
                multeOrExpression.add(subExpression);
            } else {
                for (String fullId : configData.getDataIds()) {
                    LikeExpression subExpression = new LikeExpression();
                    subExpression.withLeftExpression(MyBatisUtils.buildColumn(tableName, tableAlias, likeColumn));
                    subExpression.withRightExpression(new StringValue(fullId + "%"));
                    multeOrExpression.add(subExpression);
                }
            }
        }

        return new MultiOrExpression(multeOrExpression);

    }


}
